/* @flow */

import {queueWatcher} from './scheduler'
import Dep, {pushTarget, popTarget} from './dep'

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError
} from '../util/index'

import type {ISet} from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  //目标Vue实例
  vm: Component;
  //该属性主要用于测试，是当前监听的表达式或者函数的字符串形式，
  //如果出现错误，会通过该表达式作为输出。
  expression: string;
  //监听的属性发生改变后的回调函数
  cb: Function;
  //唯一的标识
  id: number;
  //为了发现对象内部值的变化，可以在选项参数中指定 deep: true 。注意监听数组的变动不需要这么做。
  deep: boolean;
  //该选项是在$watch方法中设置为true的，$watch方法是用户使用的添加监听的方法（可能通过直接调用$watch方法或者
  //使用vue的watch选项添加监听），该属性就是为了标记当前watcher是用户添加的，因为用户添加的监听可能是有问题的，
  //所以会为user选项为true的watcher添加错误处理。
  user: boolean;
  //从源码中看，该属性主要针对computed属性使用。
  //该属性与dirty属性配合使用，标记检测的对象为computed的内部属性。
  //但是也不能狭隘的认为该属性只服务于computed，通用的理解是标记lazy为true的函数或表达式
  //不会立即计算其值，而是等到需要获得该函数或表达式的值时再进行计算。
  lazy: boolean;
  //默认情况下，在监听的属性发生变化后，会异步调用cb回调函数，提高性能。
  //可以设置该选项为true，这样，在监听的属性发生变化后，会同步立即调用cb回调函数。
  sync: boolean;
  //对于组件的computed属性，在一次计算之后，会缓存计算结果，这样在获取computed中的内部值时，
  //就会使用缓存的结果，防止多次重复计算，而防止重复计算的方式就是通过dirty属性完成的，只要dirty
  //属性为false，就不会计算而是使用缓存值，但是一旦该computed依赖的属性的值发生变化，就会标记该computed
  //的内部属性的watcher为dirty为true，这样在下次获取computed的该内部属性的值时，就会进行计算，详情可见
  //Watcher.update()方法和core/instance/state.js-->createComputedGetter方法中对dirty的使用与解释
  dirty: boolean;
  //该选项用于标记当前watcher是否处于可用状态，该属性在teardown方法中被设置为false。
  active: boolean;
  //存放当前实例关联的依赖收集对象列表
  deps: Array<Dep>;
  //最新的依赖收集对象的列表，用于与deps比较，并更新deps列表
  newDeps: Array<Dep>;
  //deps中的dep的id集合
  depIds: ISet;
  //newDeps中的dep的id集合
  newDepIds: ISet;
  //1）当前watcher就是在监听一个函数，该属性保存该函数（比如computed下的属性中对应的函数）
  //2）当前watcher监听的是一个对象的属性，但是需要解析这个属性的访问路径（比如expOrFn的值为'a.b.c'），并生成一个函数，
  //    则该函数不仅可以解析路径，而且会调用属性的getter方法，以添加监听。
  getter: Function;
  //每个watcher的最终目的都是监听一个函数或者属性，根据这个函数或者属性获取一个值，
  //该属性用于存在这个获得的值。
  value: any;

  constructor(vm: Component,
              expOrFn: string | Function,
              cb: Function,
              options?: Object) {
    this.vm = vm
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    //注意，无论expOrFn是表达式还是函数，只要在运行时调用了某个
    //属性的get方法，就会将当前Watcher添加到对应的Dep中。
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {
        }
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }

    //如果lazy为true，则不会马上计算值，但是此时dirty属性也为true，所以
    //在访问此watcher的值时，会获取最新的值。
    //同时也说明，如果lazy不为true，则在创建watcher对象时，就会添加监听并直接获取值。
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * 该方法用于获得被监听的函数或表达式的结果值，并添加监听
   * Evaluate the getter, and re-collect dependencies.
   */
  get() {
    //将当前watcher放入Dep中
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      //调用待监听的属性的get方法，在get方法中会对watcher进行收集
      //在get方法中会调用当前watcher实例的addDep方法，将dep对象加入
      //当前watcher中。
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      //deep属性是暴露给开发人员使用的，详情可见官方文档$watch方法的介绍
      if (this.deep) {
        //循环调用value中的属性的get方法，这样就可以检测当前对象及其内部值的变化，
        //这是deep选项的用处
        traverse(value)
      }
      //恢复之前的Watcher对象，在前面代码调用pushTarget(this)时，栈顶可能存在一个Watcher。
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   * watcher监听的所有对象属性中都有对应的dep对象，dep对象的depend方法会在属性的getter方法中
   * 被调用，而depend方法会调用当前方法，也就是说调用流程是这样的：
   * watcher.get ---> 属性.get ---> dep.depend ---> watcher.addDep ---> dep.addSub
   */
  addDep(dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      //如果当前deps列表中无该dep，则将watcher添加到dep中，
      //而向watcher中添加dep是在cleanupDeps方法中完成的，
      //具体做法是直接将newDepIds和newDeps赋值给depIds和deps
      if (!this.depIds.has(id)) {
        dep.addSub(this) //将当前watcher添加到Dep中
      }
    }
  }

  /**
   * Clean up for dependency collection.
   * 该方法是在get方法中调用的，当所有的dep添加完成后才会被调用
   */
  cleanupDeps() {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      //移除无效的dep对象，在每次调用watcher实例的get方法时，
      //都会先移除不存在于最新的依赖收集对象列表中的依赖收集对象。
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    //保存最新的依赖对象列表至目标列表中，
    //并清空最新的依赖对象列表中的数据。
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   * 数据响应系统更新值的调用流程为：
   * 属性.setter ---> dep.notify ---> watcher.update
   */
  update() {
    /* istanbul ignore else */
    //可见，lazy与dirty配合使用
    if (this.lazy) {
      //标记dirty为true，这样在获取相应的函数或者表达式的值时，才会计算其值。
      //详情可见core/instance/state.js-->createComputedGetter方法中对dirty的使用与解释
      this.dirty = true
    } else if (this.sync) { //同步调用
      this.run()
    } else {
      queueWatcher(this) //异步调用
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   * 该方法用于调用当前watcher的cb回调函数
   */
  run() {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        // 对于对象，变量值保存着对象的引用，而属性的getter和setter可能在操作同一个对象，
        // 所以此时value === this.value恒成立，但是对象可能已经被修改了。
        isObject(value) ||
        //todo 为什么deep情况下也需要调用回调函数。
        //猜测：感觉这里的deep没有什么用
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          //cb回调函数一定存在吗？
          //cb回调函数一定存在，对于computed及其类型的监听，其回调函数为noop（也就是空函数），
          //而对于watcher来说（无论通过$watch方法或者vue的watch选项），都必须添加回调函数，否则会报错
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   * 该方法在createComputedGetter方法中被调用，主要用于computed属性的lazy选项的处理。
   * 当然，应该也可以模仿computed的lazy实现，完成自定义的懒加载。
   */
  evaluate() {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   * 该方法用于向当前watcher对应的所有deps中添加新的watcher。
   */
  depend() {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   * 从vm._watchers数组中以及当前watcher对应的所有deps中移除当前watcher
   */
  teardown() {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

/**
 * Recursively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 */
const seenObjects = new Set()

function traverse(val: any) {
  seenObjects.clear()
  _traverse(val, seenObjects)
}

function _traverse(val: any, seen: ISet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || !Object.isExtensible(val)) {
    return
  }
  if (val.__ob__) {
    //这里这样处理的原因的猜测：
    //val对象可能在待观察的对象中被引用多次，
    //所以需要跳过这种被引用多次的对象
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }

  //对于数组，不需要使用deep选项，但是对数组中的对象也会添加监听
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {//对于对象，进行val[keys[i]]的调用，会调用相应的get方法，完成监听对象的收集
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}
